结构体成员赋值到底是深拷贝还是浅拷贝?
来源:公众号【编程珠玑】
作者:守望先生
ID:shouwangxiansheng
在《C语言容易忽略的知识点》一文中,有读者说这种结构体复杂成员赋值的的拷贝是浅拷贝(感谢读者提出),那么到底什么是深拷贝,什么是浅拷贝?
浅拷贝
浅拷贝指的是仅拷贝对象的所有成员,而不包括其引用对象(例如指针指向的其他内容)。我们来看C和C++的例子。
C++的例子如下:
//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
class Test
{
public:
/*构造函数*/
Test():a(0)
{
std::cout<<"new b"<<std::endl;
b = new char[16];
}
/*析构函数*/
~Test()
{
std::cout<<"delete b"<<std::endl;
delete [] b;
}
private:
int a;
char *b;
};
int main()
{
Test test;
Test test0 = test;//浅拷贝
return 0;
}
运行结果:
new b
delete b
delete b
core dumped
可以看到,在拷贝test的时候,只拷贝了其成员本身的值,即a和b的值,而b只是一个指针,它指向的内容却没有被拷贝,因此我们说,它是浅拷贝。而对象test在被销毁时,会释放b指向的内存,test0被销毁时,又进行了重复释放,因此导致core dumped。
其拷贝过程如下图所示:
C语言例子:
//来源:公众号编程珠玑
//https://www.yanbinghu.com
typedef struct Member_t
{
char *p;
int c;
}Member;
typedef struct Test_t
{
int a;
Member b;
}Test;
int main(void)
{
Test test0;
test0.a = 10;
test0.b.p = malloc(16);
if(NULL == test0.b.p)
{
printf("malloc failed\n");
}
snprintf(test0.b.p,16,"hello");
test0.b.c = 24;
/*拷贝*/
Test test1;
test1.a = test0.a;
test1.b = test0.b;
/*修改*/
snprintf(test1.b.p,16,"world");
printf("%s\n",test0.b.p);
free(test0.b.p);
test0.b.p = NULL;
return 0;
}
运行结果:
world
由于其结构体成员赋值时,只拷贝其成员本身的值,即
test1.b = test0.b
只拷贝了其中的p的值和c的值,却没有拷贝p指向的内存,因此拷贝之后,两者的p指向同一片内存区域,导致通过其中一个修改就会影响另外一个的内容。因此它也是浅拷贝。(感谢在上篇中读者指出)
深拷贝
深拷贝除了拷贝其成员本身的值之外,还拷贝的成员指向的动态内存区域等类似的内容。
那么对于前面的例子,我们如何进行深拷贝呢?以C++为例,我们需要定义自己的拷贝构造函数:
Test(Test &t)
{
std::cout<<"copy"<<std::endl;
a = t.a;
b = new char[16];
/**拷贝b指向的内容**/
memcpy(b,t.b,16);
}
这里就不是拷贝指针b的值,而是拷贝指针b指向的内容。因此是深拷贝。再次运行结果:
new b
copy
delete b
delete b
这种情况下,test和test0中b的值是不一样的,但是b指向的内容是一样的。
那么C语言中怎么处理呢?自然就是需要拷贝成员b中p指向的内容了。这里就留给读者自己去实现了。
深拷贝过程如下:
C语言里的深拷贝与浅拷贝
作为使用C语言的读者来说,我觉得到没有必要去抓什么深拷贝与浅拷贝的概念,你只需要理解,C里面的赋值类的拷贝,仅仅是拷贝值而已,比如你拷贝的是指针,那么只是拷贝指针的值,指针指向的区域是不会拷贝的;而如果你拷贝的是数组,那么将会拷贝数组的值,而不是数组首地址(参考《C语言容易忽略的知识点》中的例子)。
结构体赋值
那么回到结构体赋值成员赋值的问题。根据上面的分析可以知道,如果结构体成员都是基本数据类型或者数组(非指针),那么直接赋值是没有任何问题的,而且非常地方便,而如果成员有指针类型,你又不想复制的结构体成员指向相同的内存区域,那么你就需要自己拷贝其指向的内容。
关于数组和指针,请参考《数组之谜》。
总结
默认的拷贝行为基本都是浅拷贝,即仅仅拷贝其成员值。当然如果所有成员值没有引用任何外部对象,或者引用的外部对象定义了自己的深拷贝行为,那么深拷贝和浅拷贝是一样的。如果需要拷贝值以外的内容,请自己定义拷贝行为。
最后,一张图理解深拷贝和浅拷贝:
最后关于C语言,自动动手,丰衣足食。
另外,有些概念是为了更好说明某个点,如果这个概念不能帮助你理解这个点,那么请关注这个点本身。
最后推荐C入门书:
关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源